/***********************************************************************************
Copy right:	    hqyj Tech.
Author:         jiaoyue
Date:           2023.07.01
Description:    http请求处理
***********************************************************************************/

#include <sys/types.h>
#include <sys/socket.h>
#include "custom_handle.h"
#include "cJSON.h"
#include "shmem.h"
#include "std_node.h"
#include "msg_queue_peer.h"

#define KB 1024
#define HTML_SIZE (64 * KB)
#define cJSON_free free

//普通的文本回复需要增加html头部
#define HTML_HEAD "Content-Type: text/html\r\n" \
				  "Connection: close\r\n"

static struct shm_param shm_para;
static std_node *g_nodes = NULL;//节点数组
static int g_node_count = 0;//节点数量

//初始化共享内存读取
int init_shared_memory() {
    const char *shm_name = "data";
    const size_t shm_size = 1024; 

    if (shm_init(&shm_para, shm_name, shm_size) < 0) {
        perror("shm_init failed");
        return -1;
    }
    int *node_num = (int *)shm_getaddr(&shm_para);
    g_nodes = (std_node *)(node_num+1);
    if (!g_nodes) {
        perror("shm_getaddr failed");
        return -1;
    }

    // 这里可以读取 node.json 中的 count 或使用固定值
    g_node_count = *node_num; // 示例值，实际从 node.json 或数据上报进程同步

    return 0;
}

/**
 * @brief 处理获取节点信息的请求，将节点信息以JSON格式返回给客户端
 * 
 * 该函数会检查共享内存是否已经初始化，如果未初始化则返回错误信息。
 * 若共享内存已初始化，会遍历节点数组，将每个节点的信息封装成JSON对象，
 * 再将这些JSON对象添加到一个JSON数组中，最后将该JSON数组以字符串形式发送给客户端。
 * 
 * @param sock 客户端套接字描述符，用于与客户端进行通信
 * @return 成功返回 0，共享内存未初始化返回 -1
 */
static int handle_get_nodes(int sock) {
    // 检查共享内存是否已经初始化，如果未初始化则返回错误信息
    if (!g_nodes) {
        const char *err = "{\"error\":\"shared memory not initialized\"}";
        send(sock, err, strlen(err), 0);
        return -1;
    }

    // 创建一个JSON数组，用于存储所有节点的信息
    cJSON *root = cJSON_CreateArray();
    // 遍历节点数组，将每个节点的信息添加到JSON数组中
    for (int i = 0; i < g_node_count; i++) {
        // 为每个节点创建一个JSON对象
        cJSON *item = cJSON_CreateObject();
        // 向JSON对象中添加节点的键值
        cJSON_AddNumberToObject(item, "key", g_nodes[i].key);
        // 向JSON对象中添加节点的类型
        cJSON_AddNumberToObject(item, "type", g_nodes[i].type);
        // 向JSON对象中添加节点的设备类型
        cJSON_AddNumberToObject(item, "dev_type", g_nodes[i].dev_type);

        // 根据节点类型选择显示浮点数或整数
        if (g_nodes[i].type == 3) {
            // 若节点类型为3，添加浮点数类型的值
            cJSON_AddNumberToObject(item, "value", g_nodes[i].new_val.f);
        } else {
            // 若节点类型不为3，添加整数类型的值
            cJSON_AddNumberToObject(item, "value", g_nodes[i].new_val.i);
        }

        // 将当前节点的JSON对象添加到JSON数组中
        cJSON_AddItemToArray(root, item);
    }
    // 将JSON数组转换为未格式化的JSON字符串
    char *json_str = cJSON_PrintUnformatted(root);

    // 将JSON字符串发送给客户端
    send(sock, json_str, strlen(json_str), 0);
    printf("%s\n",json_str);
    // 释放JSON字符串占用的内存
    cJSON_free(json_str);
    // 删除JSON数组，释放相关内存
    cJSON_Delete(root);
    return 0;
}
/**
 * @brief 向指定消息队列发送包含JSON格式命令的消息
 * 
 * 该函数创建一个包含特定类型和JSON格式命令的消息，并将其发送到指定的消息队列。
 * JSON命令包含两个键值对，分别是 "key" 和 "val"，对应传入的参数 key 和 val。
 * 
 * @param queue 消息队列的名称，用于指定消息发送的目标队列
 * @param key 命令中的键，将作为JSON对象的 "key" 字段的值
 * @param val 命令中的值，将作为JSON对象的 "val" 字段的值
 */
static void send_command(const char *queue, int key, int val) {
    Cmd cmd = { .type = 1 };
    cJSON *root = cJSON_CreateObject();
    cJSON_AddNumberToObject(root, "key", key);
    cJSON_AddNumberToObject(root, "val", val);
    char *json = cJSON_PrintUnformatted(root);
    strncpy(cmd.commend, json, sizeof(cmd.commend) - 1);
    msg_queue_send(queue, &cmd, sizeof(cmd), 0);
    free(json);
    cJSON_Delete(root);
}

static int handle_login(int sock, const char *input)
{
    char reply_buf[HTML_SIZE] = {0};
    char *uname = strstr(input, "username=");
    uname += strlen("username=");
    char *p = strstr(input, "password");
    *(p - 1) = '\0';
    printf("username = %s\n", uname);

    char *passwd = p + strlen("password=");
    printf("passwd = %s\n", passwd);

    if(strcmp(uname, "admin")==0 && strcmp(passwd, "25031")==0)
    {
        sprintf(reply_buf, "<script>localStorage.setItem('usr_user_name', '%s');</script>", uname);
        strcat(reply_buf, "<script>window.location.href = '/index.html';</script>");
        send(sock,reply_buf,strlen(reply_buf),0);
    }
    else
    {
        printf("web login failed\n");

        //"用户名或密码错误"提示，chrome浏览器直接输送utf-8字符流乱码，没有找到太好解决方案，先过渡
        char out[128] = {0xd3,0xc3,0xbb,0xa7,0xc3,0xfb,0xbb,0xf2,0xc3,0xdc,0xc2,0xeb,0xb4,0xed,0xce,0xf3};
        sprintf(reply_buf, "<script charset='gb2312'>alert('%s');</script>", out);
        strcat(reply_buf, "<script>window.location.href = '/login.html';</script>");
        send(sock,reply_buf,strlen(reply_buf),0);
    }

    return 0;
}

static int handle_add(int sock, const char *input)
{
    int number1, number2;
    
    //input必须是"data1=1data2=6"类似的格式，注意前端过来的字符串会有双引号
    sscanf(input, "\"data1=%ddata2=%d\"", &number1, &number2);
    printf("num1 = %d\n", number1);

    char reply_buf[HTML_SIZE] = {0};
    printf("num = %d\n", number1+number2);
    sprintf(reply_buf, "%d", number1+number2);
    printf("resp = %s\n", reply_buf);
    send(sock,reply_buf,strlen(reply_buf),0);

    return 0;
}

/**
 * @brief 处理自定义请求，在这里添加进程通信
 * @param input
 * @return
 */
int parse_and_process(int sock, const char *query_string, const char *input) {
    if (strstr(input, "username=") && strstr(input, "password=")) {
        return handle_login(sock, input);
    } 
    else if (strstr(input, "data1=") && strstr(input, "data2=")) {
        return handle_add(sock, input);
    } 
    else if (strstr(query_string, "get=nodes")) {//添加获取节点信息
        return handle_get_nodes(sock);
    }
 else if (strstr(query_string, "control=")) {
    int key = 0, val = 0;
    char type[16] = {0};
    sscanf(query_string, "control=%15[^&]&key=%d&val=%d", type, &key, &val);

    cJSON *root = cJSON_CreateObject();
    cJSON_AddNumberToObject(root, "key", key);
    cJSON_AddNumberToObject(root, "val", val);
    char *json = cJSON_PrintUnformatted(root);

    Cmd cmd = { .type = 1 };
    strncpy(cmd.commend, json, sizeof(cmd.commend) - 1);

    if (strcmp(type, "stm32") == 0)
        msg_queue_send("stm_command", &cmd, sizeof(cmd), 0);
    else if (strcmp(type, "modbus") == 0)
        msg_queue_send("mb_command", &cmd, sizeof(cmd), 0);

    free(json);
    cJSON_Delete(root);

    const char *ok = "{\"status\":\"ok\"}";
    send(sock, ok, strlen(ok), 0);
    return 0;
    }
    else {
        const char* json_response = "{\"message\": \"Hello, client!\"}";
        send(sock, json_response, strlen(json_response), 0);
    }
    return 0;
}